package com.tcs.tsrm.beans;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;


public class Cluster implements Comparable<Cluster> {
	public static HashMap<Integer, double[]> originalMatrixHashMap = new HashMap<Integer, double[]>();
	public static HashMap<Integer, double[]> nonTrivialMatrixHashMap = new HashMap<Integer, double[]>();
	public static HashMap<Integer, double[]> originalMatrixWithoutNormalisationHashMap = new HashMap<Integer, double[]>();
	public static double[][] originalMatrix;
	public static double[][] originalMatrixWithoutNormalisation;
	public static ArrayList<double[]> nonTrivialMatrix;
	private static int clusterNo = 1;
	private int clusterId;
	private double distFromOrigin;
	private double[] centroid;
	private double[] centroidSignature;
	private ArrayList<double[]> clusterMembers;
	private ArrayList<Integer> nonTrivialMatrixIndexes;
	private ArrayList<Integer> originalMatrixIndexes;

	/**
	 * @param point
	 * @param nonTrivialIndex
	 * @param thresholds
	 */
	public Cluster(double[] point, int nonTrivialIndex,
			ClusteringThresholds thresholds) {
		clusterMembers = new ArrayList<double[]>();
		nonTrivialMatrixIndexes = new ArrayList<Integer>();
		originalMatrixIndexes = new ArrayList<Integer>();
		nonTrivialMatrixIndexes.add(nonTrivialIndex);
		originalMatrixIndexes.add((int) point[1]);
		clusterId = clusterNo++;
		clusterMembers.add(point);
		centroid = getCentroid(point);
		distFromOrigin = calDistanceFromOrigin(centroid);
	}


	public double getVariance() {
		double variance = 0;
		for (double[] point : clusterMembers) {
			for (int i = 0; i < centroid.length; i++) {
				variance += Math.pow(point[i + 2] - centroid[i], 2);
			}
		}
		return variance;
	}

	public int getSupport() {
		return clusterMembers.size();
	}

	public ArrayList<Integer> getNonTrivialMatrixIndexes() {
		return nonTrivialMatrixIndexes;
	}

	/**
	 * @param nonTrivialMatrixIndexes
	 */
	public void setNonTrivialMatrixIndexes(
			ArrayList<Integer> nonTrivialMatrixIndexes) {
		this.nonTrivialMatrixIndexes = nonTrivialMatrixIndexes;
	}

	/**
	 * @param centroidR
	 * @return
	 */
	private double calDistanceFromOrigin(double[] centroidR) {
		double squaredSum = 0;
		for (double point : centroidR) {
			squaredSum += Math.pow(point, 2);
		}
		return Math.sqrt(squaredSum);
	}

	/**
	 * @param point
	 * @return
	 */
	private double[] getCentroid(double[] point) {
		double[] computedCentroid = new double[point.length - 2];
		for (int i = 0; i < computedCentroid.length; i++) {
			computedCentroid[i] = point[i + 2];
		}
		return computedCentroid;
	}

	/**
	 * @param nextPoint
	 * @param nonTrivialIndex
	 */
	public void addMember(double[] nextPoint, int nonTrivialIndex) {
		clusterMembers.add(nextPoint);
		nonTrivialMatrixIndexes.add(nonTrivialIndex);
		originalMatrixIndexes.add((int) nextPoint[1]);
		updateCentroid(nextPoint);
		updateDistFromOrigin(centroid);
	}

	public ArrayList<Integer> getOriginalMatrixIndexes() {
		return originalMatrixIndexes;
	}

	/**
	 * @param originalMatrixIndexes
	 */
	public void setOriginalMatrixIndexes(
			ArrayList<Integer> originalMatrixIndexes) {
		this.originalMatrixIndexes = originalMatrixIndexes;
	}

	/**
	 * @param centroidR
	 */
	private void updateDistFromOrigin(double[] centroidR) {
		distFromOrigin = calDistanceFromOrigin(centroidR);
	}

	/**
	 * @param nextPoint
	 */
	private void updateCentroid(double[] nextPoint) {
		int memberCount = clusterMembers.size();
		double[] sumOfMemberDims = new double[centroid.length];
		double[] newMember = nextPoint;
		double[] newCentroid = new double[centroid.length];
		for (int i = 0; i < centroid.length; i++) {
			sumOfMemberDims[i] = centroid[i] * memberCount;
			sumOfMemberDims[i] += newMember[i + 2];
			newCentroid[i] = sumOfMemberDims[i] / (memberCount + 1);
		}
		centroid = newCentroid;

	}

	public double[] getCentroid() {
		return centroid;
	}

	public double getDistFromOrigin() {
		return distFromOrigin;
	}

	public ArrayList<double[]> getClusterMembers() {
		return clusterMembers;
	}

	public String getMemberList() {
		String memberList = "";
		for (Integer member : nonTrivialMatrixIndexes) {
			memberList += member + ",";
		}
		return memberList;

	}

	public int getClusterNo() {
		return clusterNo;
	}

	public void setClusterNo(int clusterNo) {
		Cluster.clusterNo = clusterNo;
	}

	public int getClusterId() {
		return clusterId;
	}

	/**
	 * @param clusterId
	 */
	public void setClusterId(int clusterId) {
		this.clusterId = clusterId;
	}

	/**
	 * @return
	 */
	public String getSubsequences() {
		String out = "";
		for (double[] point : clusterMembers) {
			for (double d : point) {
				out += d + ",";
			}
			out += "\n";
		}
		return out;
	}

	/**
	 * @return
	 */
	public String getClusterMembersString() {
		String out = "";
		for (double[] d : clusterMembers) {
			out += (int) d[1] + ",";
		}
		return out.substring(0, out.length() - 1);
	}

	public String getAllMemberCoordinatesOfOriginalMatrix() {
		String allMemberCoordinates = "";
		double[] memberCoordinates = new double[originalMatrix[0].length];
		DecimalFormat df = new DecimalFormat("0.00");
		for (Integer nextMember : nonTrivialMatrixIndexes) {
			int originalMatrixIndex = (int) nonTrivialMatrix.get(nextMember
					.intValue())[1];
			memberCoordinates = originalMatrix[originalMatrixIndex];
			for (int i = 0; i < originalMatrix[0].length; i++) {
				allMemberCoordinates += df.format(memberCoordinates[i]) + ", ";
			}
			allMemberCoordinates += "\n";
		}
		return allMemberCoordinates;
	}

	public String getAllMemberCoordinatesOfNonTrivialMatrix() {
		String allMemberCoordinates = "";
		double[] memberCoordinates = new double[nonTrivialMatrix.get(0).length];
		DecimalFormat df = new DecimalFormat("0.00");
		for (Integer nextMember : nonTrivialMatrixIndexes) {

			memberCoordinates = nonTrivialMatrix.get(nextMember.intValue());
			for (int i = 1; i < memberCoordinates.length; i++) {
				allMemberCoordinates += df.format(memberCoordinates[i]) + ", ";
			}
			allMemberCoordinates += "\n";
		}
		return allMemberCoordinates;
	}

	public double[] getCentroidSignature() {
		return centroidSignature;
	}

	public void setCentroidSignature(double[] centroidSignature) {
		this.centroidSignature = centroidSignature;
	}

	public void computeNonTrivialSubsequences() {
		ArrayList<Integer> originalIndexes = new ArrayList<Integer>();
		HashMap<Integer, double[]> nonTrivialsubs = new HashMap<Integer, double[]>();
		Collections.sort(originalIndexes);
		boolean flag = false;
		double R = 20;
		nonTrivialsubs.put(originalIndexes.get(0),
				originalMatrixHashMap.get(originalIndexes.get(0)));
		int lastDiscNonTrivialIndex = originalIndexes.get(0);
		for (int i = 1; i < originalIndexes.size(); i++) {
			if ((originalIndexes.get(i) - lastDiscNonTrivialIndex) > 1) {
				flag = checkForPointsInRange(lastDiscNonTrivialIndex,
						originalIndexes.get(i), R);
				if (flag) {
					lastDiscNonTrivialIndex = originalIndexes.get(i);
					nonTrivialsubs.put(originalIndexes.get(i),
							originalMatrixHashMap.get(originalIndexes.get(i)));
					flag = false;
				}
			}
		}
	}

	private boolean checkForPointsInRange(Integer fromIndex, Integer uptoIndex,
			double R) {
		for (int i = fromIndex; i < uptoIndex; i++) {
			for (int j = i + 1; j < uptoIndex; j++) {
				if (computeDist(originalMatrixHashMap.get(i),
						originalMatrixHashMap.get(j)) >= R) {
					if ((computeDist(originalMatrixHashMap.get(j),
							originalMatrixHashMap.get(uptoIndex)) >= R)) {
						return true;
					}
				}
			}
		}
		return false;

	}

	private double computeDist(double[] point1, double[] point2) {
		double sum = 0;
		for (int i = 1; i < point1.length; i++) {
			sum += Math.pow(point1[i] - point2[i], 2);
		}
		return Math.sqrt(sum);
	}

	@Override
	public int compareTo(Cluster otherCluster) {
		return this.getClusterId() - otherCluster.getClusterId();
	}

}
